package com.boot2.core.utils;

import com.boot2.core.BaseEntity;
import com.boot2.core.exception.BusinessException;
import com.boot2.core.reflect.Invokers;
import javassist.*;
import lombok.extern.apachecommons.CommonsLog;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.util.CollectionUtils;

import javax.persistence.Transient;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author zhangweilin
 * @date 2018/7/18 10:03
 * @description: 反向工具类
 */
@CommonsLog
public class ReflectUtil {
    /*
     * 属性列表缓存，属性包括未标记为Transient的全部当前类和父类所有属性
     */
    private static final Map<Class<?>, List<Field>> allFieldWithParentNotTransientListCacheMap = new ConcurrentHashMap<>();

//    public static Object newInstance(Class<?> clazz) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SecurityException, IllegalArgumentException, NoSuchMethodException, InvocationTargetException {
//        Class<?> c = Class.forName(clazz.getName());
//        Object obj = c.newInstance();
//        return obj;
//    }

    public static Object newInstance(String className) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class<?> c = Class.forName(className);
        Object obj = c.newInstance();
        return obj;
    }

    public static Field[] getAllFieldList(Object o) {
        return getAllFieldList(o.getClass());
    }

    /**
     * 获取所有属性,不带父类的
     *
     * @param clazz
     * @return
     */
    public static Field[] getAllFieldList(Class<?> clazz) {
        // List<String> fieldList = new ArrayList<>();
        Field[] field = clazz.getDeclaredFields(); // 获取实体类的所有属性，返回Field数组
        // System.out.println("field: " + field);
        // field=(Field[]) ArrayUtils.removeElement(field, "serialVersionUID");
        for (int i = 0; i < field.length; i++) {
            // System.out.println("field: "+field[i].getName());
            if ("serialVersionUID".equals(field[i].getName())) {
                field = ArrayUtils.removeElement(field, field[i]);
                break;
            }
        }
        return field;
    }

    /**
     * 获取所有属性,带父类的
     *
     * @param clazz
     * @return
     */
    public static List<Field> getAllFieldList2(Class<?> clazz) {
        List<Field> fieldList = new ArrayList<>();
        Field[] field = clazz.getDeclaredFields();
        for (Field field2 : field) {
            if (!"serialVersionUID".equals(field2.getName())) {
                fieldList.add(field2);
            }
        }
        if (clazz.getSuperclass() != null && !clazz.getSuperclass().getName().equals(BaseEntity.class.getName())) {
            List<Field> fieldList0 = getAllFieldList2(clazz.getSuperclass());
            fieldList.addAll(fieldList0);
        }
        return fieldList;
    }

    /**
     * 获取所有属性,带父类的,忽略已经标记为不序列化的属性
     *
     * @param clazz
     * @return
     */
    public static List<Field> getAllFieldWithParentNotTransientList(Class<?> clazz, String... excludeRegxArr) {
        List<Field> fieldList = allFieldWithParentNotTransientListCacheMap.get(clazz);
        if (null != fieldList) {
            return fieldList;
        }
        fieldList = new ArrayList<>();
        Field[] field = clazz.getDeclaredFields();
        for (Field field2 : field) {
            //非序列化常量
            if (!"serialVersionUID".equals(field2.getName())) {
                boolean flag = true;
                for (int i = 0; null != excludeRegxArr && i < excludeRegxArr.length; i++) {
                    if (field2.getName().matches(excludeRegxArr[i])) {
                        flag = false;// 当检测到要排除的，则不添加
                    }
                }
                Transient annotation = field2.getAnnotation(Transient.class);
                if (flag && null == annotation) {
                    fieldList.add(field2);
                }
            }
        }
        Class<?> superclass = clazz.getSuperclass();
        if (superclass != null) {
            List<Field> fieldList0 = getAllFieldWithParentNotTransientList(superclass, excludeRegxArr);
            fieldList.addAll(fieldList0);
        }
        allFieldWithParentNotTransientListCacheMap.put(clazz, fieldList);
        return fieldList;
    }

    /**
     * 用于对类的字段赋值，无视private,project修饰符,无视set/get方法
     *
     * @param object 要反射的类
     * @param args   类的字段名和值 每个字段名和值用英文逗号隔开
     * @return
     */
    @SuppressWarnings("unchecked")
    public static Object setPrivateField(Object object, String... args) {
        try {
            Class<?> obj = object.getClass();
            Field[] fields = obj.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                fields[i].setAccessible(true);
                for (int j = 0; j < args.length; j++) {
                    String str = args[j];
                    String strs[] = str.split(",");
                    if (strs[0].equals(fields[i].getName())) {
                        fields[i].set(object, strs[1]);
                        break;
                    }
                }
            }
            return object;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void makeFieldCanAccess(Field field) throws NoSuchFieldException, IllegalAccessException {
        field.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        if (Modifier.isFinal(field.getModifiers())) {
            // System.out.println("是final");

            modifiersField.set(field, field.getModifiers() & ~Modifier.FINAL);
            // modifiersField.set(field, field.getModifiers() &
            // ~Modifier.STATIC); //静态无须考虑，静态不属于实例
        }
    }

    /**
     * 读取常量值
     *
     * @param clazz
     * @param clazz2
     * @param propertyName
     * @return
     */
    public static <T> T readConstantProperty(Class<?> clazz, Class<? extends T> clazz2, String propertyName) {
        try {
            Field field = clazz.getDeclaredField(propertyName);
            makeFieldCanAccess(field);
            return (T) field.get(null);
        } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 获取所有属性,带父类的
     *
     * @param clazz
     * @param excludeRegxArr,如.*1$ 要排除的字段
     * @return
     */
    public static List<Field> getAllFieldListWithParent(Class<?> clazz, String... excludeRegxArr) {
        List<Field> fieldList = new ArrayList<>();
        Field[] field = clazz.getDeclaredFields();
        for (Field field2 : field) {
            if (!"serialVersionUID".equals(field2.getName())) {
                boolean flag = true;
                for (int i = 0; null != excludeRegxArr && i < excludeRegxArr.length; i++) {
                    // if (excludeRegxArr[i].equals(field2.getName())) {
                    if (field2.getName().matches(excludeRegxArr[i])) {
                        flag = false;// 当检测到要排除的，则不添加
                    }
                }
                if (flag) {
                    fieldList.add(field2);
                }
            }
        }
        if (clazz.getSuperclass() != null) {
            List<Field> fieldList0 = getAllFieldListWithParent(clazz.getSuperclass(), excludeRegxArr);
            fieldList.addAll(fieldList0);
        }
        return fieldList;
    }

    /**
     * 获取所有属性,带父类的
     *
     * @param clazz
     * @param excludeRegxArr,如.*1$ 要排除的字段
     * @return
     */
    public static List<Field> getAllFieldListWithParentAndAnnotation(Class<?> clazz, Class annotationClass, String... excludeRegxArr) {
        List<Field> fieldList = new ArrayList<>();
        Field[] field = clazz.getDeclaredFields();
        for (Field field2 : field) {
            if (!"serialVersionUID".equals(field2.getName())) {
                Annotation annotation = field2.getAnnotation(annotationClass);
                if (null == annotation) {
                    continue;
                }
                boolean flag = true;
                for (int i = 0; null != excludeRegxArr && i < excludeRegxArr.length; i++) {
                    // if (excludeRegxArr[i].equals(field2.getName())) {
                    if (field2.getName().matches(excludeRegxArr[i])) {
                        flag = false;// 当检测到要排除的，则不添加
                    }
                }
                if (flag) {
                    fieldList.add(field2);
                }
            }
        }
        if (clazz.getSuperclass() != null) {
            List<Field> fieldList0 = getAllFieldListWithParentAndAnnotation(clazz.getSuperclass(), annotationClass, excludeRegxArr);
            fieldList.addAll(fieldList0);
        }
        return fieldList;
    }

    /**
     * 读取常量值
     *
     * @param clazz
     * @param clazz2
     * @param propertyName
     * @return
     */
    public static <T> T readConstantProperty(Class<?> clazz, String propertyName) {
        try {
            Field field = clazz.getDeclaredField(propertyName);
            makeFieldCanAccess(field);
            return (T) field.get(null);
        } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 判断一个类是否为基本数据类型。
     *
     * @param clazz 要判断的类。
     * @return true 表示为基本数据类型。
     */
    public static boolean isBaseDataType(Class clazz) {
        return (
                // clazz.equals(String.class) ||
                // clazz.equals(Integer.class)||
                // clazz.equals(Byte.class) ||
                // clazz.equals(Long.class) ||
                // clazz.equals(Double.class) ||
                // clazz.equals(Float.class) ||
                // clazz.equals(Character.class) ||
                // clazz.equals(Short.class) ||
                // clazz.equals(BigDecimal.class) ||
                // clazz.equals(BigInteger.class) ||
                // clazz.equals(Boolean.class) ||
                // clazz.equals(Date.class) ||
                // clazz.equals(DateTime.class) ||
                // clazz.isPrimitive()

                clazz.equals(String.class) || clazz.equals(Integer.class) || clazz.equals(Byte.class) || clazz.equals(Long.class) || clazz.equals(Double.class) || clazz.equals(Float.class) || clazz.equals(Character.class) || clazz.equals(Short.class) || clazz.equals(Boolean.class)
                        || clazz.equals(int.class) || clazz.equals(double.class) || clazz.equals(long.class) || clazz.equals(short.class) || clazz.equals(byte.class) || clazz.equals(boolean.class) || clazz.equals(char.class) || clazz.equals(float.class) || clazz.isPrimitive());
    }

    /**
     * 获取一个get方法
     *
     * @param objectClass
     * @param fieldName
     * @return
     */
    public static Method getGetMethod(Class<?> objectClass, String fieldName) {
        StringBuffer sb = new StringBuffer();
        sb.append("get");
        sb.append(fieldName.substring(0, 1).toUpperCase());
        sb.append(fieldName.substring(1));
        try {
            return objectClass.getMethod(sb.toString());
        } catch (Exception e) {
        }
        return null;
    }


    /**
     * 使用原class创建代理类并增加一个属性
     *
     * @param clazz
     * @param modifiers
     * @param typeClassName
     * @param fieldName0
     * @return
     * @throws NotFoundException
     * @throws CannotCompileException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public static <T> T newProxyInstanceAndAddField(Class<T> clazz, int modifiers, String typeClassName, String fieldName) throws NotFoundException, CannotCompileException, IllegalAccessException, InstantiationException {

        //创建ClassPool
        ClassPool classPool = ClassPool.getDefault();
        ClassClassPath classPath = new ClassClassPath(clazz);
        //防止在不同环境，找不到类,人为添加环境变量
        classPool.insertClassPath(classPath);
        CtClass ctClass = getCtClass(clazz, classPool);

        addField(modifiers, typeClassName, fieldName, classPool, ctClass);

        //加载clazz类，并创建对象
        Class<?> c = ctClass.toClass();
        T object = (T) c.newInstance();
        return object;
    }

    /**
     * 使用原class创建代理类
     *
     * @param clazz
     * @return
     * @throws NotFoundException
     * @throws CannotCompileException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public static <T> T newProxyInstance(Class<T> clazz) throws NotFoundException, CannotCompileException, IllegalAccessException, InstantiationException {
        //创建ClassPool
        ClassPool classPool = ClassPool.getDefault();
        ClassClassPath classPath = new ClassClassPath(clazz);
        classPool.insertClassPath(classPath);
        CtClass ctClass = getCtClass(clazz, classPool);

        //加载clazz类，并创建对象
        Class<?> c = ctClass.toClass();
        T object = (T) c.newInstance();
        return object;
    }

    /**
     * 创建属性
     *
     * @param modifiers
     * @param typeClassName
     * @param fieldName
     * @param classPool
     * @param ctClass
     * @throws CannotCompileException
     * @throws NotFoundException
     */
    private static void addField(int modifiers, String typeClassName, String fieldName, ClassPool classPool, CtClass ctClass) throws CannotCompileException, NotFoundException {
        CtField ctField = new CtField(classPool.get(typeClassName), fieldName, ctClass);
        //指定该字段 修饰
        ctField.setModifiers(modifiers);
        ctClass.addField(ctField);
    }

    /**
     * 创建 ClassPoolAndCtClass
     *
     * @param clazz
     * @param classPool
     * @return
     * @throws CannotCompileException
     * @throws NotFoundException
     */
    private static CtClass getCtClass(Class<?> clazz, ClassPool classPool) throws CannotCompileException, NotFoundException {
        Field[] allFieldList = ReflectUtil.getAllFieldList(clazz);
        String classname = clazz.getName() + "JavaassistProxy" + RandomUtils.randomString(10);
        CtClass ctClass = classPool.makeClass(classname);
        CtClass superclass = classPool.get(clazz.getName());
        ctClass.setSuperclass(superclass);

//        //==
//        Object[] annotations = superclass.getAnnotations();
//        for (Object annotation : annotations) {
//            System.out.println("annotation0 = " + annotation);
//        }
//
//        Object[] annotations2 = ctClass.getAnnotations();
//        for (Object annotation : annotations2) {
//            System.out.println("annotation1 = " + annotation);
//        }
//
//        ConstPool constPool = ctClass.getClassFile().getConstPool();
//
//        Annotation[] annotations1 = clazz.getAnnotations();
//        for (Annotation annotation : annotations1) {
//            System.out.println("annotation2 = " + annotation);
//            AnnotationUtil.addAnnotationToClass(ctClass, annotation);
//        }
//
//        Object[] annotations3 = ctClass.getAnnotations();
//        for (Object annotation : annotations3) {
//            System.out.println("annotation3 = " + annotation);
//        }

//        javassist.bytecode.annotation.Annotation scopeAnnotation = new javassist.bytecode.annotation.Annotation(constPool,ctClass);
//        System.out.println("scopeAnnotation = " + scopeAnnotation);

//===
        for (Field field : allFieldList) {
            //创建字段，指定了字段类型、字段名称、字段所属的类
            String fieldName0 = field.getName();
            CtField ctField = new CtField(classPool.get(field.getType().getName()), fieldName0, ctClass);
            //指定该字段 修饰
            ctField.setModifiers(field.getModifiers());
            //设置prop字段的getter/setter方法
            ctClass.addMethod(CtNewMethod.getter("get" + StringUtils.changFirstWord(fieldName0, StringUtils.toUpperCase), ctField));
            ctClass.addMethod(CtNewMethod.setter("set" + StringUtils.changFirstWord(fieldName0, StringUtils.toUpperCase), ctField));
            ctClass.addField(ctField);
        }
        return ctClass;
    }

    /**
     * 使用反射设置变量值
     *
     * @param target    被调用对象
     * @param fieldName 被调用对象的字段，一般是成员变量或静态变量，不可是常量！
     * @param value     值
     * @param <T>       value类型，泛型
     */
    public static <T> void setFieldValue(Object target, String fieldName, T value) {
        try {

            Field f = getField(target, fieldName);
            f.setAccessible(true);
            f.set(target, value);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
            throw new RuntimeException("给field设置值失败", e);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("给field设置值失败", e);
        }
    }
    /**
     * 使用反射设置变量值,如果为null的话
     *
     * @param target    被调用对象
     * @param fieldName 被调用对象的字段，一般是成员变量或静态变量，不可是常量！
     * @param value     值
     * @param <T>       value类型，泛型
     */
    public static <T> void setFieldValueIfNull(Object target, String fieldName, T value) {
        try {
            Field f = getField(target, fieldName);
            if (f == null) {
                log.warn("实例【"+target+"】找不到属性【"+fieldName+"】,忽略");
                return;
            }
            f.setAccessible(true);
            Object value0 = (T) f.get(target);
            if (null==value0) {
                f.set(target, value);
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
            throw new RuntimeException("给field设置值失败", e);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("给field设置值失败", e);
        }
    }

    public static Field getField(Object target, String fieldName) throws NoSuchFieldException {
        Class<?> c = target.getClass();
        while (Object.class != c && c != null){
            try {
                Field field = c.getDeclaredField(fieldName);
                return field;
            } catch (NoSuchFieldException | SecurityException e) {
//                e.printStackTrace();
                log.debug(c.getName()+"中未找到属性"+fieldName+"，尝试到父类中找");
            }
            c = c.getSuperclass();
        }
        return null;
    }

    /**
     * 使用反射设置变量值
     *
     * @param target    被调用对象
     * @param fieldName 被调用对象的字段，一般是成员变量或静态变量，不可是常量！
     * @param value     值
     * @param <T>       value类型，泛型
     */
    public static <T> void setFieldValue(Object target, Field field, T value) {
        try {
            Field f = getField(target, field.getName());
            f.setAccessible(true);
            f.set(target, value);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
            throw new RuntimeException("给field设置值失败", e);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("给field设置值失败", e);
        }
    }
    /**
     * 使用反射设置变量值
     *
     * @param target    被调用对象
     * @param fieldName 被调用对象的字段，一般是成员变量或静态变量，不可是常量！
     * @param value     值
     * @param <T>       value类型，泛型
     */
    public static <T> void setFieldValueIfNull(Object target, Field field, T value) {
        try {
            Field f = getField(target, field.getName());
            f.setAccessible(true);
            Object value0 = f.get(target);
            if (null==value0) {
                f.set(target, value);
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
            throw new RuntimeException("给field设置值失败", e);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("给field设置值失败", e);
        }
    }


    /**
     * 使用反射设置变量值
     *
     * @param target    被调用对象
     * @param fieldName 被调用对象的字段，一般是成员变量或静态变量，不可是常量！
     * @param value     值
     * @param <T>       value类型，泛型
     */
    public static <T> void setFieldValueBySetter(Object target, Field field, T value) {
        Method setMethod = null;
        try {

            setMethod = target.getClass().getMethod("set" + StringUtils.changFirstWord(field.getName(), StringUtils.toUpperCase), field.getType());
        } catch (NoSuchMethodException | SecurityException e1) {
            e1.printStackTrace();
            throw new BusinessException(e1);
        }
        Invokers.Invoker set = Invokers.newInvoker(setMethod);
        set.invoke(target, new Object[]{value});
    }

    /**
     * 使用反射获取变量值
     *
     * @param target    被调用对象
     * @param fieldName 被调用对象的字段，一般是成员变量或静态变量，不可以是常量
     * @param <T>       返回类型，泛型
     * @return 值
     */
    public static <T> T getFieldValue(Object target, String fieldName) {
        T value = null;
        try {
            Field f = getField(target, fieldName);
            assert f != null;
            f.setAccessible(true);
            value = (T) f.get(target);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return value;
    }

    public static <T> T getFieldValue(Object target, Field field) {
        T value = null;
        try {
            Class<?> c = target.getClass();
            field.setAccessible(true);
            value = (T) field.get(target);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return value;
    }

    public static <T> T getFieldValueByGetter(Object target, Field field) {
        Method getMethod = null;
        try {
            getMethod = target.getClass().getMethod("get" + StringUtils.changFirstWord(field.getName(), StringUtils.toUpperCase));
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        Invokers.Invoker get = Invokers.newInvoker(getMethod);
        Object getValue = get.invoke(target, null);
        return (T) getValue;
    }

    /**
     * 读取指定类里指定注解的值
     *
     * @param target
     * @param annotationClass
     * @param <T>
     * @return
     */
    public static <T> T getFieldValue(Object target, Class<? extends Annotation> annotationClass) {
        List<Field> fieldList = ReflectUtil.getAllFieldListWithParentAndAnnotation(target.getClass(), annotationClass);
        if (!CollectionUtils.isEmpty(fieldList)) {
            Field field = fieldList.get(0);
            Object value = ReflectUtil.getFieldValue(target, field);
            return (T) value;
        }
        return null;
    }


}
